/*
 * moleculer
 * Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer)
 * MIT Licensed
 */

"use strict";

const BaseSerializer = require("./base");
const P = require("../packets");

function createSchemas() {
	const avro = require("avsc");
	const schemas = {};

	schemas[P.PACKET_EVENT] = avro.Type.forSchema({
		name: P.PACKET_EVENT,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "id", type: "string" },
			{ name: "event", type: "string" },
			{ name: "data", type: ["null", "bytes"], default: null },
			{ name: "dataType", type: "int" },
			{
				name: "groups",
				type: [
					"null",
					{
						type: "array",
						items: "string"
					}
				],
				default: null
			},
			{ name: "broadcast", type: "boolean" },
			{ name: "meta", type: "string" },
			{ name: "level", type: "int" },
			{ name: "tracing", type: ["null", "boolean"], default: null },
			{ name: "parentID", type: ["null", "string"], default: null },
			{ name: "requestID", type: ["null", "string"], default: null },
			{ name: "stream", type: ["null", "boolean"], default: null },
			{ name: "seq", type: ["null", "int"], default: null },
			{ name: "caller", type: ["null", "string"], default: null },
			{ name: "needAck", type: ["null", "boolean"], default: null }
		]
	});

	schemas[P.PACKET_REQUEST] = avro.Type.forSchema({
		name: P.PACKET_REQUEST,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "id", type: "string" },
			{ name: "action", type: "string" },
			{ name: "params", type: ["null", "bytes"], default: null },
			{ name: "paramsType", type: "int" },
			{ name: "meta", type: "string" },
			{ name: "timeout", type: ["null", "double"], default: null },
			{ name: "level", type: "int" },
			{ name: "tracing", type: ["null", "boolean"], default: null },
			{ name: "parentID", type: ["null", "string"], default: null },
			{ name: "requestID", type: ["null", "string"], default: null },
			{ name: "stream", type: ["null", "boolean"], default: null },
			{ name: "seq", type: ["null", "int"], default: null },
			{ name: "caller", type: ["null", "string"], default: null }
		]
	});

	schemas[P.PACKET_RESPONSE] = avro.Type.forSchema({
		name: P.PACKET_RESPONSE,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "id", type: "string" },
			{ name: "success", type: "boolean" },
			{ name: "data", type: ["null", "bytes"], default: null },
			{ name: "dataType", type: "int" },
			{ name: "error", type: ["null", "string"], default: null },
			{ name: "meta", type: "string" },
			{ name: "stream", type: ["null", "boolean"], default: null },
			{ name: "seq", type: ["null", "int"], default: null }
		]
	});

	schemas[P.PACKET_DISCOVER] = avro.Type.forSchema({
		name: P.PACKET_DISCOVER,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" }
		]
	});

	schemas[P.PACKET_INFO] = avro.Type.forSchema({
		name: P.PACKET_INFO,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "services", type: "string" },
			{ name: "config", type: "string" },
			{
				name: "ipList",
				type: {
					type: "array",
					items: "string"
				}
			},
			{ name: "hostname", type: "string" },
			{
				name: "client",
				type: {
					type: "record",
					fields: [
						{ name: "type", type: "string" },
						{ name: "version", type: "string" },
						{ name: "langVersion", type: "string" }
					]
				}
			},
			{ name: "seq", type: "int" },
			{ name: "instanceID", type: "string" },
			{ name: "metadata", type: "string" }
		]
	});

	schemas[P.PACKET_DISCONNECT] = avro.Type.forSchema({
		name: P.PACKET_DISCONNECT,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" }
		]
	});

	schemas[P.PACKET_HEARTBEAT] = avro.Type.forSchema({
		name: P.PACKET_HEARTBEAT,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "cpu", type: "double" }
		]
	});

	schemas[P.PACKET_PING] = avro.Type.forSchema({
		name: P.PACKET_PING,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "time", type: "long" },
			{ name: "id", type: ["null", "string"], default: null }
		]
	});

	schemas[P.PACKET_PONG] = avro.Type.forSchema({
		name: P.PACKET_PONG,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "time", type: "long" },
			{ name: "arrived", type: "long" },
			{ name: "id", type: ["null", "string"], default: null }
		]
	});

	schemas[P.PACKET_GOSSIP_HELLO] = avro.Type.forSchema({
		name: P.PACKET_GOSSIP_HELLO,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "host", type: "string" },
			{ name: "port", type: "int" }
		]
	});

	schemas[P.PACKET_GOSSIP_REQ] = avro.Type.forSchema({
		name: P.PACKET_GOSSIP_REQ,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "online", type: ["null", "string"], default: null },
			{ name: "offline", type: ["null", "string"], default: null }
		]
	});

	schemas[P.PACKET_GOSSIP_RES] = avro.Type.forSchema({
		name: P.PACKET_GOSSIP_RES,
		type: "record",
		fields: [
			{ name: "ver", type: "string" },
			{ name: "sender", type: "string" },
			{ name: "online", type: ["null", "string"], default: null },
			{ name: "offline", type: ["null", "string"], default: null }
		]
	});

	return schemas;
}

/**
 * Avro serializer for Moleculer
 *
 * https://github.com/mtth/avsc
 *
 * @class AvroSerializer
 */
class AvroSerializer extends BaseSerializer {
	/**
	 * Initialize Serializer
	 *
	 * @param {any} broker
	 *
	 * @memberof Serializer
	 */
	init(broker) {
		super.init(broker);

		try {
			require("avsc");
		} catch (err) {
			/* istanbul ignore next */
			this.broker.fatal(
				"The 'avsc' package is missing! Please install it with 'npm install avsc --save' command!",
				err,
				true
			);
		}

		this.schemas = createSchemas(broker);
	}

	/**
	 * Serializer a JS object to Buffer
	 *
	 * @param {Object} obj
	 * @param {String} type of packet
	 * @returns {Buffer}
	 *
	 * @memberof Serializer
	 */
	serialize(obj, type) {
		this.serializeCustomFields(type, obj);

		const t = this.schemas[type].toBuffer(obj);

		return t;
	}

	/**
	 * Deserialize Buffer to JS object
	 *
	 * @param {Buffer} buf
	 * @param {String} type of packet
	 * @returns {Object}
	 *
	 * @memberof Serializer
	 */
	deserialize(buf, type) {
		const obj = this.schemas[type].fromBuffer(buf);

		this.deserializeCustomFields(type, obj);

		return obj;
	}
}

module.exports = AvroSerializer;
